diff options
Diffstat (limited to 'app/[lng]')
4 files changed, 612 insertions, 64 deletions
diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx index 6b058b37..d8f04095 100644 --- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/page.tsx @@ -1,10 +1,12 @@ import { vendorMdgService } from "@/lib/vendors/mdg-service" +import { getVendorDetailById } from "@/lib/vendors/service" import { VendorBasicInfo } from "./vendor-basic-info" +import { VendorEvCpInfo } from "./vendor-evcp-info" interface VendorBasicPageProps { params: { lng: string - // 협력업체 ID: 여기서는 Oracle의 벤더 코드(VNDRCD)를 사용 + // 협력업체 ID: 여기서는 eVCP의 벤더 ID를 사용 id: string } } @@ -13,10 +15,13 @@ export default async function VendorBasicPage(props: VendorBasicPageProps) { const resolvedParams = await props.params const vendorId = resolvedParams.id + // eVCP 벤더 정보 조회 + const evcpVendorDetails = await getVendorDetailById(parseInt(vendorId)) + // Oracle에서 벤더 상세 정보 조회 (ID로 조회) - const vendorDetails = await vendorMdgService.getVendorDetailsByVendorId(vendorId) + const oracleVendorDetails = await vendorMdgService.getVendorDetailsByVendorId(vendorId) - if (!vendorDetails) { + if (!evcpVendorDetails && !oracleVendorDetails) { return ( <div className="space-y-6"> <div className="text-center py-12"> @@ -32,8 +37,16 @@ export default async function VendorBasicPage(props: VendorBasicPageProps) { } return ( - <div className="space-y-6"> - <VendorBasicInfo vendorDetails={vendorDetails} /> + <div className="space-y-8"> + {/* eVCP 벤더 정보 */} + {evcpVendorDetails && ( + <VendorEvCpInfo vendorDetails={evcpVendorDetails} /> + )} + + {/* Oracle 벤더 정보 */} + {oracleVendorDetails && ( + <VendorBasicInfo vendorDetails={oracleVendorDetails} /> + )} </div> ) }
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx new file mode 100644 index 00000000..a3507dd0 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/text-utils.tsx @@ -0,0 +1,131 @@ +"use client" + +import { useState } from "react" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" +import { ChevronDown, ChevronUp } from "lucide-react" + +export function TruncatedText({ + text, + maxLength = 50, + showTooltip = true +}: { + text: string | null + maxLength?: number + showTooltip?: boolean +}) { + if (!text) return <span className="text-muted-foreground">-</span> + + if (text.length <= maxLength) { + return <span>{text}</span> + } + + const truncated = text.slice(0, maxLength) + "..." + + if (!showTooltip) { + return <span>{truncated}</span> + } + + return ( + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <span className="cursor-help border-b border-dotted border-gray-400"> + {truncated} + </span> + </TooltipTrigger> + <TooltipContent className="max-w-xs"> + <p className="whitespace-pre-wrap">{text}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + ) +} + +export function ExpandableText({ + text, + maxLength = 100, + className = "" +}: { + text: string | null + maxLength?: number + className?: string +}) { + const [isExpanded, setIsExpanded] = useState(false) + + if (!text) return <span className="text-muted-foreground">-</span> + + if (text.length <= maxLength) { + return <span className={className}>{text}</span> + } + + return ( + <Collapsible open={isExpanded} onOpenChange={setIsExpanded}> + <div className={className}> + <CollapsibleTrigger asChild> + <button className="text-left w-full group"> + <span className="whitespace-pre-wrap"> + {isExpanded ? text : text.slice(0, maxLength) + "..."} + </span> + <span className="inline-flex items-center ml-2 text-blue-600 hover:text-blue-800"> + {isExpanded ? ( + <> + <ChevronUp className="w-3 h-3" /> + <span className="text-xs ml-1">접기</span> + </> + ) : ( + <> + <ChevronDown className="w-3 h-3" /> + <span className="text-xs ml-1">더보기</span> + </> + )} + </span> + </button> + </CollapsibleTrigger> + </div> + </Collapsible> + ) +} + +export function AddressDisplay({ + address, + addressEng, + postalCode, + addressDetail +}: { + address: string | null + addressEng: string | null + postalCode: string | null + addressDetail: string | null +}) { + const hasAnyAddress = address || addressEng || postalCode || addressDetail + + if (!hasAnyAddress) { + return <span className="text-muted-foreground">-</span> + } + + return ( + <div className="space-y-1"> + {postalCode && ( + <div className="text-xs text-muted-foreground"> + 우편번호: {postalCode} + </div> + )} + {address && ( + <div className="font-medium break-words"> + {address} + </div> + )} + {addressDetail && ( + <div className="text-sm text-muted-foreground break-words"> + {addressDetail} + </div> + )} + {addressEng && ( + <div className="text-sm text-muted-foreground break-words italic"> + {addressEng} + </div> + )} + </div> + ) +}
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx index 16f75bcb..e9cbd8be 100644 --- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-basic-info.tsx @@ -3,14 +3,13 @@ import { useState, useTransition, useMemo } from "react" import { useParams } from "next/navigation" import { toast } from "sonner" -import { Separator } from "@/components/ui/separator" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { AddressDisplay } from "@/components/ui/text-utils" +import { AddressDisplay } from "./text-utils" import { Dialog, DialogContent, @@ -86,7 +85,7 @@ interface VendorBasicInfoProps { export function VendorBasicInfo({ vendorDetails }: VendorBasicInfoProps) { const params = useParams() - const vendorId = params.id as string + const vendorId = params?.id as string const [isEditing, setIsEditing] = useState(false) const [editData, setEditData] = useState(vendorDetails) const [isPending, startTransition] = useTransition() @@ -186,23 +185,23 @@ export function VendorBasicInfo({ vendorDetails }: VendorBasicInfoProps) { const result = await updateMdgVendorBasicInfo({ vendorId, updateData: { - VNDRNM_1: editData.VNDRNM_1, - VNDRNM_2: editData.VNDRNM_2, - VNDRNM_ABRV_1: editData.VNDRNM_ABRV_1, - BIZR_NO: editData.BIZR_NO, - CO_REG_NO: editData.CO_REG_NO, - CO_VLM: editData.CO_VLM, - REPR_NM: editData.REPR_NM, - REP_TEL_NO: editData.REP_TEL_NO, - REPR_RESNO: editData.REPR_RESNO, - REPRESENTATIVE_EMAIL: editData.REPRESENTATIVE_EMAIL, - BIZTP: editData.BIZTP, - BIZCON: editData.BIZCON, - NTN_CD: editData.NTN_CD, - ADR_1: editData.ADR_1, - ADR_2: editData.ADR_2, - POSTAL_CODE: editData.POSTAL_CODE, - ADDR_DETAIL_1: editData.ADDR_DETAIL_1, + VNDRNM_1: editData.VNDRNM_1 || undefined, + VNDRNM_2: editData.VNDRNM_2 || undefined, + VNDRNM_ABRV_1: editData.VNDRNM_ABRV_1 || undefined, + BIZR_NO: editData.BIZR_NO || undefined, + CO_REG_NO: editData.CO_REG_NO || undefined, + CO_VLM: editData.CO_VLM || undefined, + REPR_NM: editData.REPR_NM || undefined, + REP_TEL_NO: editData.REP_TEL_NO || undefined, + REPR_RESNO: editData.REPR_RESNO || undefined, + REPRESENTATIVE_EMAIL: editData.REPRESENTATIVE_EMAIL || undefined, + BIZTP: editData.BIZTP || undefined, + BIZCON: editData.BIZCON || undefined, + NTN_CD: editData.NTN_CD || undefined, + ADR_1: editData.ADR_1 || undefined, + ADR_2: editData.ADR_2 || undefined, + POSTAL_CODE: editData.POSTAL_CODE || undefined, + ADDR_DETAIL_1: editData.ADDR_DETAIL_1 || undefined, } }) @@ -365,47 +364,56 @@ export function VendorBasicInfo({ vendorDetails }: VendorBasicInfoProps) { return ( <> {/* 헤더 */} - <div className="flex items-center justify-between"> - <div className="min-w-0 flex-1"> - <h3 className="text-2xl font-bold tracking-tight break-words"> - {editData.VNDRNM_1 || '업체명 없음'} - </h3> - <p className="text-muted-foreground"> - 벤더 코드: {editData.VNDRCD} - </p> - </div> - <div className="flex items-center space-x-4"> - {/* 상태 배지 */} - <div className="flex items-center space-x-2"> - {getStatusBadge(editData.DEL_ORDR)} + <Card className="border-l-4 border-l-orange-500"> + <CardHeader> + <div className="flex items-center justify-between"> + <div className="min-w-0 flex-1"> + <CardTitle className="text-2xl font-bold tracking-tight break-words flex items-center gap-2"> + <Building2 className="w-6 h-6 text-orange-600" /> + {editData.VNDRNM_1 || '업체명 없음'} + <Badge variant="outline" className="text-xs bg-orange-50 text-orange-700">Oracle</Badge> + </CardTitle> + <CardDescription className="text-base"> + 벤더 코드: {editData.VNDRCD} + </CardDescription> + </div> + <div className="flex items-center space-x-4"> + {/* 상태 배지 */} + <div className="flex items-center space-x-2"> + {getStatusBadge(editData.DEL_ORDR)} + </div> + + {/* 액션 버튼들 */} + <div className="flex items-center space-x-2"> + {isEditing ? ( + <> + <Button onClick={handleEditSave} size="sm" disabled={showConfirmDialog}> + <Save className="w-4 h-4 mr-2" /> + 저장 + </Button> + <Button onClick={handleEditCancel} variant="outline" size="sm" disabled={showConfirmDialog || isPending}> + <X className="w-4 h-4 mr-2" /> + 취소 + </Button> + </> + ) : ( + <> + <Button onClick={handleEditStart} variant="outline" size="sm"> + <Edit className="w-4 h-4 mr-2" /> + 수정 + </Button> + </> + )} + </div> + </div> </div> - - {/* 액션 버튼들 */} - <div className="flex items-center space-x-2"> - {isEditing ? ( - <> - <Button onClick={handleEditSave} size="sm" disabled={showConfirmDialog}> - <Save className="w-4 h-4 mr-2" /> - 저장 - </Button> - <Button onClick={handleEditCancel} variant="outline" size="sm" disabled={showConfirmDialog || isPending}> - <X className="w-4 h-4 mr-2" /> - 취소 - </Button> - </> - ) : ( - <> - <Button onClick={handleEditStart} variant="outline" size="sm"> - <Edit className="w-4 h-4 mr-2" /> - 수정 - </Button> - </> - )} + </CardHeader> + <CardContent> + <div className="text-sm text-muted-foreground"> + Oracle 시스템에서 연동된 협력업체 정보입니다. 수정 시 Oracle에 반영됩니다. </div> - </div> - </div> - - <Separator /> + </CardContent> + </Card> <div className="grid gap-6 md:grid-cols-2"> {/* 기본 정보 */} diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-evcp-info.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-evcp-info.tsx new file mode 100644 index 00000000..4da3162a --- /dev/null +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/vendor-evcp-info.tsx @@ -0,0 +1,396 @@ +"use client" + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { AddressDisplay } from "./text-utils" +import { + Phone, + Mail, + Calendar, + CheckCircle, + XCircle, + Building2, + Globe, + User, + FileText, + TrendingUp, + Hash, + MapPin, + Users, + Award, + Briefcase, + Shield, + Star, + DollarSign +} from "lucide-react" +import { VendorDetailView } from "@/db/schema/vendors" + +interface VendorEvCpInfoProps { + vendorDetails: VendorDetailView +} + +// 신용평가기관 표시 매핑 +const creditAgencyMap: Record<string, string> = { + NICE: "NICE평가정보", + KIS: "KIS (한국신용평가)", + KED: "KED (한국기업데이터)", + SCI: "SCI평가정보", +} + +// 사업규모 표시 개선 +const businessSizeMap: Record<string, { label: string; color: string }> = { + "LARGE": { label: "대기업", color: "text-purple-600" }, + "MEDIUM": { label: "중견기업", color: "text-blue-600" }, + "SMALL": { label: "중소기업", color: "text-green-600" }, + "STARTUP": { label: "스타트업", color: "text-orange-600" }, +} + +export function VendorEvCpInfo({ vendorDetails }: VendorEvCpInfoProps) { + // 연락처 정보 파싱 + const contacts = Array.isArray(vendorDetails.contacts) + ? vendorDetails.contacts + : typeof vendorDetails.contacts === 'string' + ? JSON.parse(vendorDetails.contacts || '[]') + : [] + + // 첨부파일 정보 파싱 + const attachments = Array.isArray(vendorDetails.attachments) + ? vendorDetails.attachments + : typeof vendorDetails.attachments === 'string' + ? JSON.parse(vendorDetails.attachments || '[]') + : [] + + // 상태에 따른 뱃지 스타일 결정 + const getStatusBadge = (status: string) => { + switch (status) { + case 'ACTIVE': + return <Badge variant="default" className="bg-green-100 text-green-800"><CheckCircle className="w-3 h-3 mr-1" />활성</Badge> + case 'INACTIVE': + return <Badge variant="secondary" className="bg-gray-100 text-gray-600"><XCircle className="w-3 h-3 mr-1" />비활성</Badge> + case 'PENDING_REVIEW': + return <Badge variant="outline" className="bg-yellow-50 text-yellow-700 border-yellow-300">검토중</Badge> + case 'APPROVED': + return <Badge variant="default" className="bg-blue-100 text-blue-800">승인됨</Badge> + case 'BLACKLISTED': + return <Badge variant="destructive">거래금지</Badge> + default: + return <Badge variant="outline">{status}</Badge> + } + } + + // 신용등급 색상 결정 + const getCreditRatingColor = (rating: string) => { + if (rating?.includes('AAA')) return "text-green-600 bg-green-50" + if (rating?.includes('AA')) return "text-blue-600 bg-blue-50" + if (rating?.includes('A')) return "text-indigo-600 bg-indigo-50" + if (rating?.includes('BBB')) return "text-yellow-600 bg-yellow-50" + if (rating?.includes('BB') || rating?.includes('B')) return "text-orange-600 bg-orange-50" + return "text-gray-600 bg-gray-50" + } + + // 필드 렌더링 헬퍼 + const renderField = (label: string, value: React.ReactNode, icon?: React.ReactNode) => { + if (!value) return null + return ( + <div className="space-y-1"> + <label className="text-sm font-medium text-muted-foreground flex items-center gap-1"> + {icon} + {label} + </label> + <div className="text-sm"> + {value} + </div> + </div> + ) + } + + return ( + <div className="space-y-6"> + {/* 헤더 */} + <Card className="border-l-4 border-l-blue-500"> + <CardHeader> + <div className="flex items-center justify-between"> + <div className="min-w-0 flex-1"> + <CardTitle className="text-2xl font-bold tracking-tight break-words flex items-center gap-2"> + <Building2 className="w-6 h-6 text-blue-600" /> + {vendorDetails.vendorName || '업체명 없음'} + <Badge variant="outline" className="text-xs bg-blue-50 text-blue-700 border-blue-300"> + eVCP + </Badge> + </CardTitle> + <CardDescription className="flex items-center gap-4 mt-2"> + <span>벤더 코드: {vendorDetails.vendorCode || 'N/A'}</span> + <span>사업자번호: {vendorDetails.taxId || 'N/A'}</span> + </CardDescription> + </div> + <div className="flex items-center space-x-2"> + {getStatusBadge(vendorDetails.status || 'ACTIVE')} + </div> + </div> + </CardHeader> + </Card> + + <div className="grid gap-6 md:grid-cols-2"> + {/* 기본 정보 */} + <Card> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + 기본 정보 + </CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + {renderField("업체명", vendorDetails.vendorName)} + {renderField("업체 코드", vendorDetails.vendorCode, <Hash className="w-3 h-3" />)} + {renderField("사업자등록번호", vendorDetails.taxId, <FileText className="w-3 h-3" />)} + {renderField("법인등록번호", vendorDetails.corporateRegistrationNumber, <FileText className="w-3 h-3" />)} + {renderField("국가", vendorDetails.country, <MapPin className="w-3 h-3" />)} + + {/* 사업규모 */} + {vendorDetails.businessSize && ( + <div className="space-y-1"> + <label className="text-sm font-medium text-muted-foreground flex items-center gap-1"> + <Briefcase className="w-3 h-3" /> + 사업규모 + </label> + <div className="text-sm"> + <span className={`font-medium ${businessSizeMap[vendorDetails.businessSize]?.color || 'text-gray-600'}`}> + {businessSizeMap[vendorDetails.businessSize]?.label || vendorDetails.businessSize} + </span> + </div> + </div> + )} + + {/* 등록일 */} + {renderField("등록일", + vendorDetails.createdAt ? new Date(vendorDetails.createdAt).toLocaleDateString('ko-KR') : null, + <Calendar className="w-3 h-3" /> + )} + </CardContent> + </Card> + + {/* 연락처 정보 */} + <Card> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + 연락처 정보 + </CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + {renderField("전화번호", vendorDetails.phone, <Phone className="w-3 h-3" />)} + {renderField("이메일", + vendorDetails.email && ( + <a + href={`mailto:${vendorDetails.email}`} + className="text-blue-600 hover:underline break-all" + > + {vendorDetails.email} + </a> + ), + <Mail className="w-3 h-3" /> + )} + + <div> + <label className="text-sm font-medium text-muted-foreground">웹사이트</label> + <p className="text-sm break-words"> + {vendorDetails.website ? ( + <div className="flex items-center gap-1"> + <Globe className="w-3 h-3" /> + <a + href={vendorDetails.website.startsWith('http') ? vendorDetails.website : `https://${vendorDetails.website}`} + target="_blank" + rel="noopener noreferrer" + className="text-blue-600 hover:underline break-all" + > + {vendorDetails.website} + </a> + </div> + ) : ( + <span className="text-muted-foreground">정보 없음</span> + )} + </p> + </div> + + {renderField("주소", + vendorDetails.address && <AddressDisplay address={vendorDetails.address} />, + <MapPin className="w-3 h-3" /> + )} + </CardContent> + </Card> + + {/* 대표자 정보 */} + {(vendorDetails.representativeName || vendorDetails.representativeEmail || vendorDetails.representativePhone) && ( + <Card> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + 대표자 정보 + </CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + {renderField("대표자명", vendorDetails.representativeName, <User className="w-3 h-3" />)} + {renderField("대표자 이메일", + vendorDetails.representativeEmail && ( + <a + href={`mailto:${vendorDetails.representativeEmail}`} + className="text-blue-600 hover:underline break-all" + > + {vendorDetails.representativeEmail} + </a> + ), + <Mail className="w-3 h-3" /> + )} + {renderField("대표자 전화번호", vendorDetails.representativePhone, <Phone className="w-3 h-3" />)} + {renderField("대표자 생년월일", vendorDetails.representativeBirth, <Calendar className="w-3 h-3" />)} + </CardContent> + </Card> + )} + + {/* 신용평가 정보 */} + {(vendorDetails.creditAgency || vendorDetails.creditRating || vendorDetails.cashFlowRating) && ( + <Card> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + <Award className="w-5 h-5" /> + 신용평가 정보 + </CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + {renderField("신용평가기관", + vendorDetails.creditAgency && creditAgencyMap[vendorDetails.creditAgency] || vendorDetails.creditAgency, + <Shield className="w-3 h-3" /> + )} + + {vendorDetails.creditRating && ( + <div className="space-y-1"> + <label className="text-sm font-medium text-muted-foreground flex items-center gap-1"> + <Star className="w-3 h-3" /> + 신용등급 + </label> + <div className="text-sm"> + <Badge className={`${getCreditRatingColor(vendorDetails.creditRating)} border-0`}> + {vendorDetails.creditRating} + </Badge> + </div> + </div> + )} + + {vendorDetails.cashFlowRating && ( + <div className="space-y-1"> + <label className="text-sm font-medium text-muted-foreground flex items-center gap-1"> + <TrendingUp className="w-3 h-3" /> + 현금흐름등급 + </label> + <div className="text-sm"> + <Badge variant="outline" className="bg-green-50 text-green-700 border-green-300"> + {vendorDetails.cashFlowRating} + </Badge> + </div> + </div> + )} + </CardContent> + </Card> + )} + + {/* 제공 서비스/품목 */} + {vendorDetails.items && ( + <Card className="md:col-span-2"> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + <Briefcase className="w-5 h-5" /> + 제공 서비스/품목 + </CardTitle> + </CardHeader> + <CardContent> + <div className="text-sm whitespace-pre-wrap bg-gray-50 p-3 rounded-md border"> + {vendorDetails.items} + </div> + </CardContent> + </Card> + )} + + {/* 등록된 연락처 */} + {contacts.length > 0 && ( + <Card className="md:col-span-2"> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + <Users className="w-5 h-5" /> + 등록된 연락처 ({contacts.length}개) + </CardTitle> + </CardHeader> + <CardContent> + <div className="grid gap-3 md:grid-cols-2"> + {contacts.map((contact: any, index: number) => ( + <div key={contact.id || index} className="p-3 bg-gray-50 rounded-md border"> + <div className="flex items-center justify-between mb-2"> + <span className="font-medium">{contact.contactName}</span> + {contact.isPrimary && ( + <Badge variant="default" className="text-xs">주 담당자</Badge> + )} + </div> + {contact.contactPosition && ( + <p className="text-sm text-muted-foreground mb-1"> + {contact.contactPosition} + </p> + )} + <div className="space-y-1 text-sm"> + {contact.contactEmail && ( + <div className="flex items-center gap-1"> + <Mail className="w-3 h-3" /> + <a + href={`mailto:${contact.contactEmail}`} + className="text-blue-600 hover:underline" + > + {contact.contactEmail} + </a> + </div> + )} + {contact.contactPhone && ( + <div className="flex items-center gap-1"> + <Phone className="w-3 h-3" /> + <span>{contact.contactPhone}</span> + </div> + )} + </div> + </div> + ))} + </div> + </CardContent> + </Card> + )} + + {/* 첨부파일 정보 */} + {attachments.length > 0 && ( + <Card className="md:col-span-2"> + <CardHeader> + <CardTitle className="text-lg flex items-center gap-2"> + <FileText className="w-5 h-5" /> + 첨부파일 ({attachments.length}개) + </CardTitle> + </CardHeader> + <CardContent> + <div className="space-y-2"> + {attachments.map((attachment: any, index: number) => ( + <div key={attachment.id || index} className="flex items-center justify-between p-2 bg-gray-50 rounded border"> + <div className="flex items-center gap-2"> + <FileText className="w-4 h-4 text-gray-500" /> + <span className="text-sm font-medium">{attachment.fileName}</span> + </div> + <div className="flex items-center gap-2"> + <Badge variant="outline" className="text-xs"> + {attachment.attachmentType || 'GENERAL'} + </Badge> + {attachment.createdAt && ( + <span className="text-xs text-muted-foreground"> + {new Date(attachment.createdAt).toLocaleDateString('ko-KR')} + </span> + )} + </div> + </div> + ))} + </div> + </CardContent> + </Card> + )} + </div> + </div> + ) +}
\ No newline at end of file |
